﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Dorm.Maps;

namespace Dorm.QueryObject
{
    class ScriptService
    {
        public static List<string> ManyToManyTables { get; set; }

        public string ScriptAll()
        {
            Type[] mapTypes = MapLocator.GetAllEntityMapTypes();
            ManyToManyTables = new List<string>();

            var allTablesScripts = new List<Create>();
            var script = new StringBuilder();

            foreach (var mapType in mapTypes.Where(t => t.BaseType.Name.StartsWith("EntityMap")))
            {
                Type entityType = mapType.BaseType.GetGenericArguments()[0];
                var tableScript = new Create(entityType, false);
                script.AppendLine(tableScript.ToString());
                allTablesScripts.Add(tableScript);
            }

            foreach (var tableScript in allTablesScripts.Where(t => !string.IsNullOrEmpty(t.ManyToManyScript)))
            {
                script.AppendLine(tableScript.ManyToManyScript);
            }

            foreach(var tableScript in allTablesScripts)
            {
                if (!string.IsNullOrEmpty(tableScript.ReferenceFKs))
                    script.AppendLine(tableScript.ReferenceFKs);
            }

            return script.ToString();
        }
    }

    class Create
    {
        public Type EntityType { get; set; }
        public bool IncludeFKs { get; set; }
        private StringBuilder script;
        private List<MappedProperty> properties;
        private string tab = "    ";

        public string ReferenceFKs { get; set; }
        public string ManyToManyScript { get; set; }

        public Create(Type entityType) : this(entityType, true) { }

        public Create(Type entityType, bool includeFKs)
        {
            EntityType = entityType;
            IncludeFKs = includeFKs;
            script = new StringBuilder();
            properties = MapLocator.GetMap(EntityType);
        }

        public override string ToString()
        {
            AppendHeader();
            AppendFields();
            AppendPK();
            AppendReferenceFKs();

            if (properties.Any(p => p.MappingType == MappedProperty.MappingTypes.HasManyToMany))
                GenerateManyToMany();

            return script.ToString();
        }

        private void AppendHeader()
        {
            script.AppendLine("CREATE TABLE " + EntityType.Name);
            script.AppendLine("(");
        }

        private void AppendFields()
        {
            script.AppendLine(tab + "ID bigint IDENTITY(1,1) NOT NULL,");

            foreach(var property in properties.Where(p => p.MappingType == MappedProperty.MappingTypes.Simple 
                                                       || p.MappingType == MappedProperty.MappingTypes.Reference))
            {
                AppendField(property, property.PropertyName);
            }

            foreach(var component in properties.Where(p => p.MappingType == MappedProperty.MappingTypes.Component))
            {
                var componentMaps = MapLocator.GetMap(component.PropertyType);

                foreach (var componentProperty in componentMaps.Where(p => p.MappingType == MappedProperty.MappingTypes.Simple
                                           || p.MappingType == MappedProperty.MappingTypes.Reference))
                {
                    AppendField(componentProperty, component.PropertyName + componentProperty.PropertyName);
                }
            }
        }

        private void AppendField(MappedProperty property, string propertyName)
        {
            script.Append(tab + propertyName);
                
            if (property.MappingType == MappedProperty.MappingTypes.Reference)
                script.Append("ID");

            script.Append(" " + property.GetDbTypeName());
                
            if (property.Length > 0)
                script.Append(" (" + property.Length + ") ");
                
            script.AppendLine(" NULL,");
        }

        private void AppendPK()
        {
            script.AppendLine(tab + "CONSTRAINT PK_" + EntityType.Name + " PRIMARY KEY CLUSTERED (ID ASC)");
            //script.AppendLine(tab + "WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]");
            script.AppendLine(")  ");
            //script.AppendLine("ON [PRIMARY]");
        }

        private void AppendReferenceFKs()
        {
            var scriptFKs = new StringBuilder();

            foreach(var referenceProperty in properties.Where(p => p.MappingType == MappedProperty.MappingTypes.Reference))
            {
                scriptFKs.AppendLine();
                scriptFKs.AppendLine("ALTER TABLE " + EntityType.Name + " WITH CHECK ");
                scriptFKs.AppendLine("ADD CONSTRAINT FK_" + EntityType.Name + "_" + referenceProperty.PropertyName);
                scriptFKs.AppendLine("FOREIGN KEY(" + referenceProperty.PropertyName + "ID) REFERENCES " + referenceProperty.PropertyType.Name + " (ID)");
            }

            foreach (var component in properties.Where(p => p.MappingType == MappedProperty.MappingTypes.Component))
            {
                var componentMaps = MapLocator.GetMap(component.PropertyType);

                foreach (var componentProperty in componentMaps.Where(p => p.MappingType == MappedProperty.MappingTypes.Reference))
                {
                    scriptFKs.AppendLine();
                    scriptFKs.AppendLine("ALTER TABLE " + EntityType.Name + " WITH CHECK ");
                    scriptFKs.AppendLine("ADD CONSTRAINT FK_" + EntityType.Name + "_" + component.PropertyName + componentProperty.PropertyName);
                    scriptFKs.AppendLine("FOREIGN KEY(" + component.PropertyName + componentProperty.PropertyName + "ID) REFERENCES " + componentProperty.PropertyType.Name + " (ID)");
                }
            }

            ReferenceFKs = scriptFKs.ToString();

            if (IncludeFKs)
                script.AppendLine(ReferenceFKs);
        }

        private void GenerateManyToMany()
        {
            var scriptMM = new StringBuilder();

            foreach(var property in properties.Where(p => p.MappingType == MappedProperty.MappingTypes.HasManyToMany))
            {
                string manyToManyTableName = MappedProperty
                    .GetManyToManyTableName(EntityType.Name, property.PropertyType.Name);

                if (!property.IsFirstOnManyToMany(EntityType.Name) && ScriptService.ManyToManyTables != null && ScriptService.ManyToManyTables.Contains(manyToManyTableName))
                    continue;

                scriptMM.AppendLine();
                scriptMM.AppendLine("CREATE TABLE " + manyToManyTableName);
                scriptMM.AppendLine("(");
                scriptMM.AppendLine(tab + EntityType.Name + "ID bigint NOT NULL,");
                scriptMM.AppendLine(tab + property.PropertyType.Name + "ID bigint NOT NULL,");
                scriptMM.AppendLine(")");

                scriptMM.AppendLine();

                scriptMM.AppendLine("ALTER TABLE " + manyToManyTableName + " WITH CHECK ");
                scriptMM.AppendLine("ADD CONSTRAINT FK_" + manyToManyTableName + "_" + EntityType.Name);
                scriptMM.AppendLine("FOREIGN KEY(" + EntityType.Name + "ID) REFERENCES " + EntityType.Name + " (ID)");

                scriptMM.AppendLine();

                scriptMM.AppendLine("ALTER TABLE " + manyToManyTableName + " WITH CHECK ");
                scriptMM.AppendLine("ADD CONSTRAINT FK_" + manyToManyTableName + "_" + property.PropertyType.Name);
                scriptMM.AppendLine("FOREIGN KEY(" + property.PropertyType.Name + "ID) REFERENCES " + property.PropertyType.Name + " (ID)");

                ScriptService.ManyToManyTables.Add(manyToManyTableName);
            }

            ManyToManyScript = scriptMM.ToString();
        }
    }
}
